﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PServiceBus.Services.Web.DTO;
using PServiceBus.Services.Gateway.Runtime;
using PServiceBus.Core.Runtime;
using PServiceBus.Core.Runtime.Extensions;
using PServiceBus.Services.Gateway.Extensions;
using PServiceBus.Gateway.Interfaces;
using GatewayDTO = PServiceBus.Gateway.Interfaces.DTO;
using PServiceBus.Core.Runtime.Serializers;

namespace PServiceBus.Services.Web {
    partial class WebMessageBus {

        private void ValidateSubscriptionInfo(SubscriptionInfo info) {
            if (info == null) throw new ArgumentNullException("info");
            if (info.Subscriber == null) throw new InvalidOperationException("info.Subscriber");
            if (info.Topic == null) throw new InvalidOperationException("info.Topic");
        }

        private string ClassNameAlias {
            get {
                return ID.Replace("-", string.Empty)
                    .Replace(".", string.Empty).Replace(" ", string.Empty);
            }
        }

        public void PublishTopic(PublishTopicInfo info) {
            if (info == null) throw new ArgumentNullException("info");
            if (info.Topic == null) throw new InvalidOperationException("info.Topic");
            if (info.Messages == null) throw new InvalidOperationException("info.Messages");
            var name = info.Topic;
            if (!MessageBus.SelectTopic(name).IsValid) return;
            var expiresIn = info.ExpiresIn;
            var topic = Topic.New(name);
            topic.Headers = info.Headers ?? new Dictionary<string, string>();
            topic.SetMessageExpiration(info.ExpiresIn);
            foreach (var dict in info.Messages) {
                var publishDict = new Dictionary<string, object>();
                foreach (var param in dict) {
                    if (param.Key == _classIdentifier) continue;
                    var paramValue = param.Value;
                    if (paramValue == null) continue;
                    if (paramValue is JsonObject) {
                        paramValue = (paramValue as JsonObject).Members;
                    }
                    var isDict = paramValue.GetType() == _dictType;
                    var valueDict = isDict ? paramValue as Dictionary<string, object> : null;
                    var isRealDict = isDict && valueDict.ContainsKey(_typeIdentifier)
                        && valueDict.Get<string>(_typeIdentifier) == _dictIdentifier;
                    var isClass = isDict && !isRealDict;
                    object value = null;
                    if (isClass) {
                        var className = param.Key + valueDict.GenerateClassName();
                        value = ReflectionHelper.GenerateClass(className, valueDict);
                    } else {
                        if (isRealDict) {
                            valueDict.Remove(_typeIdentifier);
                            value = valueDict;
                        } else value = paramValue;
                    }
                    value = value.ObjectToJson().Translate();
                    publishDict[param.Key] = value;
                }
                topic.PublishDicts.Add(publishDict);
            }
            MethodHelper.Try(() => MessageBus.PublishTopic(topic.ToTopicInfo()));
        }


        public void CreateSubscriber(string name) {
            var subscriber = MessageBus.SelectSubscriber(name);
            if (subscriber.IsValid) return;
            MessageBus.CreateSubscriber(name);
        }

        public void ChangeSubscriberDurability(SubscriptionInfo info) {
            InvokeIfSubscriberExists(info.Subscriber, bus => {
                MessageBus.ChangeSubscriberDurability(info.Subscriber, info.Durable);
            });
        }

        public void Update(SubscriptionInfo info) {
            ValidateSubscriptionInfo(info);
            InvokeIfSubscriberExists(info.Subscriber, bus => {
                MessageBus.UpdateSubscriber(info.Subscriber, info.Topic, info.Filter,
                    info.NeedHeader, info.CaseSensitive);
            });
        }

        public void UnSubscribe(SubscriptionInfo info) {
            ValidateSubscriptionInfo(info);
            InvokeIfSubscriberExists(info.Subscriber, bus => {
                MessageBus.UnSubscribeFrom(info.Subscriber, info.Topic);
            });
        }

        public void Subscribe(SubscriptionInfo info) {
            ValidateSubscriptionInfo(info);
            var name = info.Subscriber;
            var subscriber = MessageBus.SelectSubscriber(name).ToSubscriber();
            if (!subscriber.IsValid)
                MessageBus.CreateSubscriber(name);
            subscriber = MessageBus.SelectSubscriber(name).ToSubscriber();
            if (!subscriber.IsSubscribeTo(info.Topic)) {
                //Handle wildcard patterns
                if (_topicPatternRegex.IsMatch(info.Topic)) {
                    var topic = SelectTopic(info.Topic);
                    if (!topic.IsValid) 
                        RegisterTopic(new TopicInfo { Name = info.Topic, Description = info.Topic });
                }

                MessageBus.SubscribeTo(name, info.Topic, info.Filter, info.NeedHeader, info.CaseSensitive);
                if (info.Transport != null)
                    MessageBus.AddTransport(name, info.Transport.Name, info.Transport.ToTransport(), info.Topic);
            }
        }

        public void AddTransport(SubscriptionInfo info) {
            ValidateSubscriptionInfo(info);
            if (info.Transport == null) throw new InvalidOperationException("info.Transport");
            InvokeIfSubscriberExists(info.Subscriber, bus => {
                MessageBus.AddTransport(info.Subscriber, info.Transport.Name, info.Transport.ToTransport(), info.Topic);
            });
        }

        public void DeleteTransport(SubscriptionInfo info) {
            if (info == null) throw new ArgumentNullException("info");
            if (info.Subscriber == null) throw new InvalidOperationException("info.Subscriber");
            if (info.Transport == null) throw new InvalidOperationException("info.Transport");
            InvokeIfSubscriberExists(info.Subscriber, bus => {
                MessageBus.DeleteTransport(info.Subscriber, info.Transport.Name);
            });
        }

        public Subscriber SelectSubscriber(string name) {
            if (name == null) throw new ArgumentNullException("name");
            return MessageBus.SelectSubscriber(name).ToSubscriber();
        }

        public Topic SelectTopic(string name) {
            if (name == null) throw new ArgumentNullException("name");
            return MessageBus.SelectTopic(name).ToTopic();
        }

        public bool Ping() {
            return !MessageBus.IsClosed;
        }

        public void DeleteSubscriber(string name) {
            if (name == null) throw new ArgumentNullException("name");
            InvokeIfSubscriberExists(name, bus => {
                MessageBus.DeleteSubscriber(name);
            });
        }

        public void DeleteTopic(string name) {
            if (name == null) throw new ArgumentNullException("name");
            InvokeIfTopicExists(name, bus => {
                MethodHelper.Try(() => {
                    MessageBus.DeleteTopic(name);
                });
            });
        }

        public void RegisterTopic(TopicInfo info) {
            if (info == null) throw new ArgumentNullException("info");
            if (info.Name == null) throw new InvalidOperationException("info.Name");
            var name = info.Name;
            var topic = MessageBus.SelectTopic(name);
            if (topic.IsValid) return;
            info.Description = info.Description ?? name;
            info.Contract = info.Contract ?? new Dictionary<string, string>();
            topic = new GatewayDTO.TopicInfo() { Name = name, Description = info.Description, Contract = info.Contract };
            MessageBus.RegisterTopic(topic);
        }

        public IList<Topic> GetTopics() {
            return MessageBus.GetTopics().Select(x => x.ToTopic()).ToList();
        }

        private void InvokeIfTopicExists(string name, Action<IMessageBus> action) {
            if (name == null) throw new ArgumentNullException("name");
            if (action == null) throw new ArgumentNullException("action");
            var topic = MessageBus.SelectTopic(name);
            if (topic == null || !topic.IsValid) return;
            action(MessageBus);
        }

        private void InvokeIfSubscriberExists(string name, Action<IMessageBus> action){
            if (name == null) throw new ArgumentNullException("name");
            if (action == null) throw new ArgumentNullException("action");
            var subscriber = MessageBus.SelectSubscriber(name);
            if (subscriber == null || !subscriber.IsValid) return;
            action(MessageBus);
        }
    }
}
